﻿#include "pch.h"
#include <iostream>

#ifdef _MSC_VER
#include <Windows.h>
#pragma warning(disable : 4819)
#pragma warning(disable : 4996)
#endif

#include <stdio.h>
#include <assert.h>
#include <math.h>
#include <time.h>

extern "C" {
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>

#include <libswscale/swscale.h>
}

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#ifdef _MSC_VER
#undef main
#endif

#ifdef _MSC_VER
#pragma warning(disable : 4244)
#pragma comment(lib, "avformat")
#pragma comment(lib, "avcodec")
#pragma comment(lib, "avutil")
#pragma comment(lib, "swscale")
#endif

int FFmpeg_Code01(int argc, char * argv[]);
int FFmpeg_from_Chasing(int argc, char * argv[]);

int get_error_msg(int code, std::string& msg) {
    std::string str_err_msg; 
    str_err_msg.resize(256);
    int ret = av_strerror(code, (char*)str_err_msg.data(), str_err_msg.length());
    msg = str_err_msg.c_str();
    return ret;
}

int main(int argc, char * argv[])
{
    int ret = 0;
    do
    {
        printf("Press Enter to next loop.\n");
        getchar();

        //ret = FFmpeg_Code01(argc, argv);
        ret = FFmpeg_from_Chasing(argc, argv);
        
    } while (true);
    return ret;
}

int CreateAndFillFrame(AVFrame** ppFrameYUV, int width, int height, const uint8_t *pBuf)
{
    int ret = -1;
    *ppFrameYUV = av_frame_alloc();
    int bytes = 0;
    std::string errorMessage;
    do
    {
        ret = av_image_fill_arrays((*ppFrameYUV)->data, (*ppFrameYUV)->linesize, pBuf, AV_PIX_FMT_YUV420P, width, height, 32);
        if (ret < 0) {
            get_error_msg(ret, errorMessage);
            printf("av_image_fill_arrays failed, %s\n", errorMessage.c_str());
            break;
        }
        ret = 0;
    } while (false);
    return ret;
}

int FFmpeg_Code01(int argc, char * argv[])
{
    int ret = -1;
    int cnt = 0;
    AVFormatContext* pFormatCtx = NULL;
    AVDictionary* pDictionary = NULL;
    AVPacket packet;
    AVFrame *frame = NULL, *frameYUV = NULL;
    uint8_t *pBuf = NULL;
    struct SwsContext* swsCtx = NULL;
    bool videoCodecOpened = false;
    AVCodecContext* vCodecCtx = NULL;
    const char* path = argv[1];
    std::string errorMessage;
    FILE *fpY = NULL;
    FILE *fpU = NULL;
    FILE *fpV = NULL;

    do
    {
        av_init_packet(&packet);

        ret = avformat_open_input(&pFormatCtx, path, NULL, &pDictionary);
        if (ret != 0) {
            get_error_msg(ret, errorMessage);
            printf("avformat_open_input failed, %s\n", errorMessage.c_str());
            break;
        }

        int cnt = pFormatCtx->nb_streams;
        ret = avformat_find_stream_info(pFormatCtx, NULL);
        if (ret != 0) {
            get_error_msg(ret, errorMessage);
            printf("avformat_find_stream_info failed, %s\n", errorMessage.c_str());
            break;
        }

        int audioStreamIdx = -1, videoStreamIdx = -1;
        AVCodec* vCodec = NULL;
        videoStreamIdx = av_find_best_stream(pFormatCtx, AVMediaType::AVMEDIA_TYPE_VIDEO, -1, -1, &vCodec, 0);
        if (videoStreamIdx < 0) {
            printf("av_find_best_stream failed\n");
            break;
        }

        vCodecCtx = pFormatCtx->streams[videoStreamIdx]->codec;
        if (!vCodec) {
            vCodec = avcodec_find_decoder(vCodecCtx->codec_id);
            if (!vCodec) {
                printf("avcodec_find_decoder failed, codec_id = [ %d ]\n", vCodecCtx->codec_id);
                break;
            }
        }

        ret = avcodec_open2(vCodecCtx, vCodec, NULL);
        if (ret < 0) {
            get_error_msg(ret, errorMessage);
            printf("avcodec_open2 failed, %s\n", errorMessage.c_str());
            break;
        }
        videoCodecOpened = true;

        frame    = av_frame_alloc();
        frameYUV = av_frame_alloc();

        int width = vCodecCtx->width;
        int height = vCodecCtx->height;
        int bytes = 0;

        bytes = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, width, height, 32);
        pBuf = (uint8_t *)av_malloc(bytes * sizeof(uint8_t));
        if (!pBuf) {
            printf("av_malloc failed.\n");
            break;
        }

        ret = av_image_fill_arrays(frameYUV->data, frameYUV->linesize, pBuf, AV_PIX_FMT_YUV420P, width, height, 32);
        if (ret < 0) {
            get_error_msg(ret, errorMessage);
            printf("av_image_fill_arrays failed, %s\n", errorMessage.c_str());
            break;
        }

        swsCtx = sws_getContext(width, height, vCodecCtx->pix_fmt, width, height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
        if (!swsCtx) {
            printf("sws_getContext failed.\n");
            break;
        }

#if 0
        std::string workDir = "C:\\Work\\ffmpeg_exercise\\";
        std::string fileY = workDir + "output_YUV_Y.y";
        std::string fileU = workDir + "output_YUV_U.y";
        std::string fileV = workDir + "output_YUV_V.y";
        fpY = fopen(fileY.c_str(), "wb+");
        fpU = fopen(fileU.c_str(), "wb+");
        fpV = fopen(fileV.c_str(), "wb+");
#endif

        // 开始读取数据包
        int hasErr = 0;
        int gotPic = 0;

        int cnt_pic_packet = 0;
        int cnt_frame = 0;
        
        av_init_packet(&packet);
        while (1)
        {
            int readret = 0;
            readret = av_read_frame(pFormatCtx, &packet);
            if (readret < 0) {
                if (AVERROR_EOF == readret) {
                    printf("end of file.\n");
                    break;
                }
                get_error_msg(readret, errorMessage);
                break;
            }

            if (packet.stream_index == videoStreamIdx) {
                ret = avcodec_decode_video2(vCodecCtx, frame, &gotPic, &packet);
                if (ret  < 0) {
                    hasErr = 1;
                    printf("avcodec_decode_video2 failed.\n");
                    break;
                }

                cnt_pic_packet++;

                if (gotPic) {
                    cnt_pic_packet = 0;
                    int sliceHeight = sws_scale(swsCtx, (const uint8_t**)frame->data, frame->linesize,
                        0, height,
                        frameYUV->data, frameYUV->linesize);
#if 0
                    fwrite(frameYUV->data[0], 1, width * height, fpY);
                    fwrite(frameYUV->data[1], 1, width * height / 4, fpU);
                    fwrite(frameYUV->data[2], 1, width * height / 4, fpV);
#endif
                    cnt_frame++;
                    
                    av_frame_free(&frame);
                    frame = av_frame_alloc();

                    av_frame_free(&frameYUV);
                    ret = CreateAndFillFrame(&frameYUV, width, height, pBuf);
                    if (ret < 0) {
                        hasErr = 1;
                        break;
                    }
                }
            }// video stream data

            av_packet_unref(&packet);
            av_init_packet(&packet);
        }// while 
        if (hasErr) {
            break;
        }
        
        ret = 0;
    } while (false);

    av_packet_unref(&packet);

    if (fpY) {
        fclose(fpY); fpY = NULL;
    }
    if (fpU) {
        fclose(fpU); fpU = NULL;
    }
    if (fpV) {
        fclose(fpV); fpV = NULL;
    }
    if (swsCtx) {
        sws_freeContext(swsCtx);
    }
    if (pBuf) {
        av_freep(&pBuf);
    }
    if (frame) {
        av_frame_free(&frame);
    }
    if (frameYUV) {
        av_frame_free(&frameYUV);
    }
    if (videoCodecOpened) {
        avcodec_close(vCodecCtx);
    }
    if (pFormatCtx)
        avformat_close_input(&pFormatCtx);

    return ret;
}


int FFmpeg_from_Chasing(int argc, char * argv[])
{
    int ret = -1;
    const char* path = argv[1];
    AVPacket packet;
    AVCodec *pCodec = NULL;
    AVCodecContext *origin_ctx = NULL, *ctx = NULL;
    AVFrame *pFrame = NULL;
    AVFormatContext *fmt_ctx = NULL;

    int video_stream;
    int got_frame = 0;
    uint8_t *byte_buffer = NULL;
    int byte_buffer_size;
    int i = 0;
    int result;
    int end_of_stream = 0;

    do
    {
        /*打开输入文件*/
        result = avformat_open_input(&fmt_ctx, path, NULL, NULL);
        if (result < 0) {
            break;
        }

        /*获取文件视频流信息*/
        result = avformat_find_stream_info(fmt_ctx, NULL);
        if (result < 0) {
            break;
        }

        /*获取视频编码格式*/
        video_stream = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
        if (video_stream < 0) {
            break;
        }

        origin_ctx = fmt_ctx->streams[video_stream]->codec;

        /*查找解码器*/
        pCodec = avcodec_find_decoder(origin_ctx->codec_id);
        if (!pCodec) {
            break;
        }

        ctx = avcodec_alloc_context3(pCodec);
        if (!ctx) {
            break;
        }

        result = avcodec_copy_context(ctx, origin_ctx);
        if (result) {
            break;
        }

        /*打开解码器*/
        result = avcodec_open2(ctx, pCodec, NULL);
        if (result < 0) {
            break;
        }

        pFrame = av_frame_alloc();
        if (!pFrame) {
            break;
        }

        /*指定格式图像所需的大小*/
        byte_buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P, ctx->coded_width, ctx->coded_height, 1);
        byte_buffer = (uint8_t *)av_malloc(byte_buffer_size);
        if (!byte_buffer) {
            break;
        }

        i = 0;
        av_init_packet(&packet);

        do
        {
            if (!end_of_stream)
            {
                if (av_read_frame(fmt_ctx, &packet) < 0)
                    end_of_stream = 1;
            }

            if (end_of_stream) {
                packet.data = NULL;
                packet.size = 0;
            }
            if (packet.stream_index == video_stream || end_of_stream)
            {
                got_frame = 0;

                if (packet.pts == AV_NOPTS_VALUE)
                    packet.pts = packet.dts = i;

                result = avcodec_decode_video2(ctx, pFrame, &got_frame, &packet);
                if (result < 0) {
                    printf("Error decoding frame\n");
                    continue;
                }
                if (got_frame) {
                    byte_buffer_size = av_image_copy_to_buffer(byte_buffer, byte_buffer_size,
                        (const UINT8* const *)pFrame->data, (const int*)pFrame->linesize,
                        AV_PIX_FMT_YUV420P, ctx->coded_width, ctx->coded_height, 1);
                    if (byte_buffer_size < 0) {
                        printf("Can't copy image to buffer\n");
                        return byte_buffer_size;
                    }                                                                                                                   //dosomethinghere
                }
                av_packet_unref(&packet);
                av_init_packet(&packet);
            }
            i++;
        } while (!end_of_stream || got_frame);

        av_packet_unref(&packet);

        av_frame_free(&pFrame);
        avcodec_close(ctx);
        avformat_close_input(&fmt_ctx);
        avcodec_free_context(&ctx);
        av_freep(&byte_buffer);



        ret = 0;
    } while (false);

    return ret;
}